/*
  Google HTML5 slides template

  Authors: Luke MahÃ© (code)
           Marcin Wichary (code and design)

           Dominic Mazzoni (browser compatibility)
           Charles Chen (ChromeVox support)

  URL: http://code.google.com/p/html5slides/
*/

var PERMANENT_URL_PREFIX = 'http://black-forest-gtug-slides.googlecode.com/svn/trunk/_common/';

var SLIDE_CLASSES = ['far-past', 'past', 'current', 'next', 'far-next'];

var PM_TOUCH_SENSITIVITY = 15;

var curSlide;

/* ---------------------------------------------------------------------- */
/* classList polyfill by Eli Grey 
 * (http://purl.eligrey.com/github/classList.js/blob/master/classList.js) */

if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) {

    (function(view) {

        var
        classListProp = "classList",
            protoProp = "prototype",
            elemCtrProto = (view.HTMLElement || view.Element)[protoProp],
            objCtr = Object
            strTrim = String[protoProp].trim ||
            function() {
                return this.replace(/^\s+|\s+$/g, "");
            },
            arrIndexOf = Array[protoProp].indexOf ||
            function(item) {
                for (var i = 0, len = this.length; i < len; i++) {
                    if (i in this && this[i] === item) {
                        return i;
                    }
                }
                return -1;
            }
            // Vendors: please allow content code to instantiate DOMExceptions
            ,
            DOMEx = function(type, message) {
                this.name = type;
                this.code = DOMException[type];
                this.message = message;
            },
            checkTokenAndGetIndex = function(classList, token) {
                if (token === "") {
                    throw new DOMEx("SYNTAX_ERR", "An invalid or illegal string was specified");
                }
                if (/\s/.test(token)) {
                    throw new DOMEx("INVALID_CHARACTER_ERR", "String contains an invalid character");
                }
                return arrIndexOf.call(classList, token);
            },
            ClassList = function(elem) {
                var
                trimmedClasses = strTrim.call(elem.className),
                    classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [];
                for (var i = 0, len = classes.length; i < len; i++) {
                    this.push(classes[i]);
                }
                this._updateClassName = function() {
                    elem.className = this.toString();
                };
            },
            classListProto = ClassList[protoProp] = [],
            classListGetter = function() {
                return new ClassList(this);
            };
        // Most DOMException implementations don't allow calling DOMException's toString()
        // on non-DOMExceptions. Error's toString() is sufficient here.
        DOMEx[protoProp] = Error[protoProp];
        classListProto.item = function(i) {
            return this[i] || null;
        };
        classListProto.contains = function(token) {
            token += "";
            return checkTokenAndGetIndex(this, token) !== -1;
        };
        classListProto.add = function(token) {
            token += "";
            if (checkTokenAndGetIndex(this, token) === -1) {
                this.push(token);
                this._updateClassName();
            }
        };
        classListProto.remove = function(token) {
            token += "";
            var index = checkTokenAndGetIndex(this, token);
            if (index !== -1) {
                this.splice(index, 1);
                this._updateClassName();
            }
        };
        classListProto.toggle = function(token) {
            token += "";
            if (checkTokenAndGetIndex(this, token) === -1) {
                this.add(token);
            } else {
                this.remove(token);
            }
        };
        classListProto.toString = function() {
            return this.join(" ");
        };

        if (objCtr.defineProperty) {
            var classListPropDesc = {
                get: classListGetter,
                enumerable: true,
                configurable: true
            };
            try {
                objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
            } catch (ex) { // IE 8 doesn't support enumerable:true
                if (ex.number === -0x7FF5EC54) {
                    classListPropDesc.enumerable = false;
                    objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
                }
            }
        } else if (objCtr[protoProp].__defineGetter__) {
            elemCtrProto.__defineGetter__(classListProp, classListGetter);
        }

    }(self));

} /* ---------------------------------------------------------------------- */

/* Slide movement */

function getSlideEl(no) {
    if ((no < 0) || (no >= slideEls.length)) {
        return null;
    } else {
        return slideEls[no];
    }
};

function updateSlideClass(slideNo, className) {
    var el = getSlideEl(slideNo);

    if (!el) {
        return;
    }

    if (className) {
        el.classList.add(className);
    }

    for (var i in SLIDE_CLASSES) {
        if (className != SLIDE_CLASSES[i]) {
            el.classList.remove(SLIDE_CLASSES[i]);
        }
    }
};

function updateSlides() {
    for (var i = 0; i < slideEls.length; i++) {
        switch (i) {
        case curSlide - 2:
            updateSlideClass(i, 'far-past');
            break;
        case curSlide - 1:
            updateSlideClass(i, 'past');
            break;
        case curSlide:
            updateSlideClass(i, 'current');
            break;
        case curSlide + 1:
            updateSlideClass(i, 'next');
            break;
        case curSlide + 2:
            updateSlideClass(i, 'far-next');
            break;
        default:
            updateSlideClass(i);
            break;
        }
    }

    triggerLeaveEvent(curSlide - 1);
    triggerEnterEvent(curSlide);

    window.setTimeout(function() {
        // Hide after the slide
        disableSlideFrames(curSlide - 2);
    }, 301);

    enableSlideFrames(curSlide - 1);
    enableSlideFrames(curSlide + 2);

    if (isChromeVoxActive()) {
        speakAndSyncToNode(slideEls[curSlide]);
    }

    updateHash();
};

function buildNextItem() {
    var toBuild = slideEls[curSlide].querySelectorAll('.to-build');

    if (!toBuild.length) {
        return false;
    }

    toBuild[0].classList.remove('to-build', '');

    if (isChromeVoxActive()) {
        speakAndSyncToNode(toBuild[0]);
    }

    return true;
};

function prevSlide() {
    if (curSlide > 0) {
        curSlide--;

        updateSlides();
    }
};

function nextSlide() {
    if (buildNextItem()) {
        return;
    }

    if (curSlide < slideEls.length - 1) {
        curSlide++;

        updateSlides();
    }
};

/* Slide events */

function triggerEnterEvent(no) {
    var el = getSlideEl(no);
    if (!el) {
        return;
    }

    var onEnter = el.getAttribute('onslideenter');
    if (onEnter) {
        new Function(onEnter).call(el);
    }

    var evt = document.createEvent('Event');
    evt.initEvent('slideenter', true, true);
    evt.slideNumber = no + 1; // Make it readable
    el.dispatchEvent(evt);
};

function triggerLeaveEvent(no) {
    var el = getSlideEl(no);
    if (!el) {
        return;
    }

    var onLeave = el.getAttribute('onslideleave');
    if (onLeave) {
        new Function(onLeave).call(el);
    }

    var evt = document.createEvent('Event');
    evt.initEvent('slideleave', true, true);
    evt.slideNumber = no + 1; // Make it readable
    el.dispatchEvent(evt);
};

/* Touch events */

function handleTouchStart(event) {
    if (event.touches.length == 1) {
        touchDX = 0;
        touchDY = 0;

        touchStartX = event.touches[0].pageX;
        touchStartY = event.touches[0].pageY;

        document.body.addEventListener('touchmove', handleTouchMove, true);
        document.body.addEventListener('touchend', handleTouchEnd, true);
    }
};

function handleTouchMove(event) {
    if (event.touches.length > 1) {
        cancelTouch();
    } else {
        touchDX = event.touches[0].pageX - touchStartX;
        touchDY = event.touches[0].pageY - touchStartY;
    }
};

function handleTouchEnd(event) {
    var dx = Math.abs(touchDX);
    var dy = Math.abs(touchDY);

    if ((dx > PM_TOUCH_SENSITIVITY) && (dy < (dx * 2 / 3))) {
        if (touchDX > 0) {
            prevSlide();
        } else {
            nextSlide();
        }
    }

    cancelTouch();
};

function cancelTouch() {
    document.body.removeEventListener('touchmove', handleTouchMove, true);
    document.body.removeEventListener('touchend', handleTouchEnd, true);
};

/* Preloading frames */

function disableSlideFrames(no) {
    var el = getSlideEl(no);
    if (!el) {
        return;
    }

    var frames = el.getElementsByTagName('iframe');
    for (var i = 0, frame; frame = frames[i]; i++) {
        disableFrame(frame);
    }
};

function enableSlideFrames(no) {
    var el = getSlideEl(no);
    if (!el) {
        return;
    }

    var frames = el.getElementsByTagName('iframe');
    for (var i = 0, frame; frame = frames[i]; i++) {
        enableFrame(frame);
    }
};

function disableFrame(frame) {
    frame.src = 'about:blank';
};

function enableFrame(frame) {
    var src = frame._src;

    if (frame.src != src && src != 'about:blank') {
        frame.src = src;
    }
};

function setupFrames() {
    var frames = document.querySelectorAll('iframe');
    for (var i = 0, frame; frame = frames[i]; i++) {
        frame._src = frame.src;
        disableFrame(frame);
    }

    enableSlideFrames(curSlide);
    enableSlideFrames(curSlide + 1);
    enableSlideFrames(curSlide + 2);
};

function setupInteraction() { /* Clicking and tapping */

    var el = document.createElement('div');
    el.className = 'slide-area';
    el.id = 'prev-slide-area';
    el.addEventListener('click', prevSlide, false);
    document.querySelector('section.slides').appendChild(el);

    var el = document.createElement('div');
    el.className = 'slide-area';
    el.id = 'next-slide-area';
    el.addEventListener('click', nextSlide, false);
    document.querySelector('section.slides').appendChild(el);

    /* Swiping */

    document.body.addEventListener('touchstart', handleTouchStart, false);
}

/* ChromeVox support */

function isChromeVoxActive() {
    if (typeof(cvox) == 'undefined') {
        return false;
    } else {
        return true;
    }
};

function speakAndSyncToNode(node) {
    if (!isChromeVoxActive()) {
        return;
    }

    cvox.ChromeVox.navigationManager.switchToStrategy(
    cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM, 0, true);
    cvox.ChromeVox.navigationManager.syncToNode(node);
    cvox.ChromeVoxUserCommands.finishNavCommand('');
    var target = node;
    while (target.firstChild) {
        target = target.firstChild;
    }
    cvox.ChromeVox.navigationManager.syncToNode(target);
};

function speakNextItem() {
    if (!isChromeVoxActive()) {
        return;
    }

    cvox.ChromeVox.navigationManager.switchToStrategy(
    cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM, 0, true);
    cvox.ChromeVox.navigationManager.next(true);
    if (!cvox.DomUtil.isDescendantOfNode(
    cvox.ChromeVox.navigationManager.getCurrentNode(), slideEls[curSlide])) {
        var target = slideEls[curSlide];
        while (target.firstChild) {
            target = target.firstChild;
        }
        cvox.ChromeVox.navigationManager.syncToNode(target);
        cvox.ChromeVox.navigationManager.next(true);
    }
    cvox.ChromeVoxUserCommands.finishNavCommand('');
};

function speakPrevItem() {
    if (!isChromeVoxActive()) {
        return;
    }

    cvox.ChromeVox.navigationManager.switchToStrategy(
    cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM, 0, true);
    cvox.ChromeVox.navigationManager.previous(true);
    if (!cvox.DomUtil.isDescendantOfNode(
    cvox.ChromeVox.navigationManager.getCurrentNode(), slideEls[curSlide])) {
        var target = slideEls[curSlide];
        while (target.lastChild) {
            target = target.lastChild;
        }
        cvox.ChromeVox.navigationManager.syncToNode(target);
        cvox.ChromeVox.navigationManager.previous(true);
    }
    cvox.ChromeVoxUserCommands.finishNavCommand('');
};

/* Hash functions */

function getCurSlideFromHash() {
    var slideNo = parseInt(location.hash.substr(1));

    if (slideNo) {
        curSlide = slideNo - 1;
    } else {
        curSlide = 0;
    }
};

function updateHash() {
    location.replace('#' + (curSlide + 1));
};

/* Event listeners */

function handleBodyKeyDown(event) {
    switch (event.keyCode) {
    case 39:
        // right arrow
    case 13:
        // Enter
    case 32:
        // space
    case 34:
        // PgDn
        nextSlide();
        event.preventDefault();
        break;

    case 37:
        // left arrow
    case 8:
        // Backspace
    case 33:
        // PgUp
        prevSlide();
        event.preventDefault();
        break;

    case 40:
        // down arrow
        if (isChromeVoxActive()) {
            speakNextItem();
        } else {
            nextSlide();
        }
        event.preventDefault();
        break;

    case 38:
        // up arrow
        if (isChromeVoxActive()) {
            speakPrevItem();
        } else {
            prevSlide();
        }
        event.preventDefault();
        break;
    }
};

function addEventListeners() {
    document.addEventListener('keydown', handleBodyKeyDown, false);
};

/* Initialization */

function addPrettify() {
    var els = document.querySelectorAll('pre');
    for (var i = 0, el; el = els[i]; i++) {
        if (!el.classList.contains('noprettyprint')) {
            el.classList.add('prettyprint');
        }
    }

    var el = document.createElement('script');
    el.type = 'text/javascript';
    el.src = PERMANENT_URL_PREFIX + 'prettify.js';
    el.onload = function() {
        prettyPrint();
    }
    document.body.appendChild(el);
};

function addFontStyle() {
    var el = document.createElement('link');
    el.rel = 'stylesheet';
    el.type = 'text/css';
    el.href = 'http://fonts.googleapis.com/css?family=' + 'Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono';

    document.body.appendChild(el);
};

function addGeneralStyle() {
    var el = document.createElement('link');
    el.rel = 'stylesheet';
    el.type = 'text/css';
    el.href = PERMANENT_URL_PREFIX + 'styles.css';
    document.body.appendChild(el);

    var el = document.createElement('meta');
    el.name = 'viewport';
    el.content = 'width=1100,height=750';
    document.querySelector('head').appendChild(el);

    var el = document.createElement('meta');
    el.name = 'apple-mobile-web-app-capable';
    el.content = 'yes';
    document.querySelector('head').appendChild(el);
};

function makeBuildLists() {
    for (var i = curSlide, slide; slide = slideEls[i]; i++) {
        var items = slide.querySelectorAll('.build > *');
        for (var j = 0, item; item = items[j]; j++) {
            if (item.classList) {
                item.classList.add('to-build');
            }
        }
    }
};

function handleDomLoaded() {
    slideEls = document.querySelectorAll('section.slides > article');

    setupFrames();

    addFontStyle();
    addGeneralStyle();
    addPrettify();
    addEventListeners();

    updateSlides();

    setupInteraction();
    makeBuildLists();

    document.body.classList.add('loaded');
};

function initialize() {
    getCurSlideFromHash();

    if (window['_DEBUG']) {
        PERMANENT_URL_PREFIX = '../';
    }

    if (window['_DCL']) {
        handleDomLoaded();
    } else {
        document.addEventListener('DOMContentLoaded', handleDomLoaded, false);
    }
}

// If ?debug exists then load the script relative instead of absolute
if (!window['_DEBUG'] && document.location.href.indexOf('?debug') !== -1) {
    document.addEventListener('DOMContentLoaded', function() {
        // Avoid missing the DomContentLoaded event
        window['_DCL'] = true
    }, false);

    window['_DEBUG'] = true;
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = '../slides.js';
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(script, s);

    // Remove this script
    s.parentNode.removeChild(s);
} else {
    initialize();
}